﻿using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;

namespace Serenity.Web;

/// <summary>
/// Authorizes access to a controller action. Optionally checks the permission provided as the first argument.
/// Use special permission key "?" to check for logged-in users, and "*" to allow anyone including anonymous access.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class PageAuthorizeAttribute : TypeFilterAttribute
{
    /// <summary>
    /// Creates an instance of the PageAuthorizeAttribute.
    /// </summary>
    public PageAuthorizeAttribute()
        : base(typeof(PageAuthorizeFilter))
    {
        Arguments = [this];
    }

    private class PageAuthorizeFilter(PageAuthorizeAttribute attr) : IResourceFilter
    {
        readonly PageAuthorizeAttribute attr = attr;

        public void OnResourceExecuted(ResourceExecutedContext context)
        {
        }

        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            if ((string.IsNullOrEmpty(attr.Permission) &&
                 !context.HttpContext.User.IsLoggedIn()) ||
                (!string.IsNullOrEmpty(attr.Permission) &&
                 !context.HttpContext.RequestServices.GetRequiredService<IPermissionService>().HasPermission(attr.Permission)))
            {
                if (context.HttpContext.User.IsLoggedIn())
                    context.Result = new ForbidResult();
                else
                    context.Result = new ChallengeResult();
            }
        }
    }

    /// <summary>
    /// Creates a new instance of the class
    /// </summary>
    /// <param name="sourceType">Source type</param>
    /// <param name="attributeTypes">Attribute types</param>
    /// <exception cref="ArgumentNullException">Source type or attribute types is null</exception>
    /// <exception cref="ArgumentOutOfRangeException">One of the attribute types
    /// is not a subclass of PermissionAttributeBase.</exception>
    protected PageAuthorizeAttribute(Type sourceType, params Type[] attributeTypes)
        : this()
    {
        ArgumentNullException.ThrowIfNull(sourceType);

        if (attributeTypes.IsEmptyOrNull())
            throw new ArgumentNullException(nameof(attributeTypes));

        PermissionAttributeBase attr = null;
        foreach (var attributeType in attributeTypes)
        {
            var lst = sourceType.GetCustomAttributes(attributeType, true);
            if (lst.Length > 0)
            {
                attr = lst[0] as PermissionAttributeBase;
                if (attr == null)
                    throw new ArgumentOutOfRangeException(attributeType.Name +
                        " is not a subclass of PermissionAttributeBase!");

                break;
            }
        }

        if (attr == null)
        {
            throw new ArgumentOutOfRangeException(nameof(sourceType),
                "PageAuthorize attribute is created with source type of " +
                sourceType.Name + ", but it has no " +
                string.Join(" OR ", attributeTypes.Select(x => x.Name)) + " attribute(s)");
        }

        Permission = attr.Permission;
    }

    /// <summary>
    /// Creates an instance of the PageAuthorizeAttribute, while reading the permission
    /// key from the NavigationPermissionAttribute or ReadPermissionAttribute of the source type.
    /// </summary>
    /// <param name="sourceType"></param>
    public PageAuthorizeAttribute(Type sourceType)
        : this(sourceType, typeof(NavigationPermissionAttribute), typeof(ReadPermissionAttribute))
    {
    }

    /// <summary>
    /// Creates an instance of the PageAuthorizeAttribute with the passed permission key.
    /// </summary>
    /// <param name="permission">The permission key</param>
    public PageAuthorizeAttribute(object permission)
        : this()
    {
        Permission = permission?.ToString();
    }
    /// <summary>
    /// Creates an instance of the PageAuthorizeAttribute with a permission key
    /// generated by joining the passed permissions with a colon (:), e.g. "module:permission".
    /// </summary>
    /// <param name="module">Module</param>
    /// <param name="permission">Permission key</param>
    public PageAuthorizeAttribute(object module, object permission)
        : this(module.ToString() + ":" + permission)
    {
    }

    /// <summary>
    /// Creates an instance of the PageAuthorizeAttribute with a permission key
    /// generated by joining the passed permissions with a colon (:), e.g. "module:submodule:permission".
    /// </summary>
    /// <param name="module">Module</param>
    /// <param name="submodule">Submodule</param>
    /// <param name="permission">Permission key</param>
    public PageAuthorizeAttribute(object module, object submodule, object permission)
        : this(module.ToString() + ":" + submodule + ":" + permission)
    {
    }

    /// <summary>
    /// The permission key
    /// </summary>
    public string Permission { get; private set; }
}